﻿using System;
using System.IO;
using System.Xml.Serialization;

using QuickTuner.Util;

namespace QuickTuner
{
    /// <summary>
    /// Contains list of stations and logic to play them.
    /// </summary>
    [Serializable]
    [XmlRoot("Radio")]
    public class Radio : IRadio
    {
        private readonly StationList _stations;
        private int _currentStationIndex;
        private Version _clientVersion;

        [NonSerialized]
        private IPlayEngine _engine;

        // properties _________________________________________________________

        public Version ClientVersion
        {
            get { return _clientVersion; }
            set { _clientVersion = value; }
        }

        public StationList Stations { get { return _stations; } }

        public Station this[int index] { get { return _stations[index]; } }

        public int CurrentStationIndex
        {
            get
            {
                return EnsureCurrentStation()
                    ? _currentStationIndex
                    : -1;
            }
            set
            {
                _currentStationIndex = value;
                OnCurrentStationChanged(new EventArgs());
            }
        }

        [XmlIgnore]
        public Station CurrentStation
        {
            get
            {
                return EnsureCurrentStation()
                    ? _stations[_currentStationIndex]
                    : null;
            }
            set
            {
                _currentStationIndex = _stations.IndexOf(value);
                OnCurrentStationChanged(new EventArgs());
            }
        }

        [XmlIgnore]
        public IPlayEngine PlayEngine
        {
            get
            {
                EnsurePlayEngine();
                return _engine;
            }
            set
            {
                if (_engine != null && _engine.IsPlaying)
                    Stop();

                _engine = value;
            }
        }

        public bool IsPlaying
        {
            get
            {
                if (!EnsurePlayEngine())
                    throw new InvalidOperationException(
                        "Cannot access the play engine, thus IsPlaying could not be" +
                        " determined. Make sure that a play engine can be created" +
                        " properly.");

                return _engine.IsPlaying;
            }
        }

        // events _____________________________________________________________

        [field: NonSerialized]
        public event EventHandler CurrentStationChanged;
        [field: NonSerialized]
        public event EventHandler PlayStateChanged;

        protected virtual void OnCurrentStationChanged(EventArgs e)
        {
            // if the radio is already playing, we need to re-issue
            // the play command so the engine would play the new url
            if (_currentStationIndex != -1 && _engine != null && _engine.IsPlaying)
                Play();

            // raise the event
            if (CurrentStationChanged != null)
                CurrentStationChanged(this, e);
        }

        protected virtual void OnPlayStateChanged(EventArgs e)
        {
            if (PlayStateChanged != null)
                PlayStateChanged(this, e);
        }

        // constructors _______________________________________________________

        public Radio() : this(null, null) { }
        public Radio(IPlayEngine playEngine) : this(playEngine, null) { }
        public Radio(StationList stationsSource) : this(null, stationsSource) { }

        public Radio(IPlayEngine playEngine, StationList stationsSource)
        {
            _engine = playEngine;
            _stations = stationsSource ?? new StationList();
        }

        // methods ____________________________________________________________

        // Helpers

        /// <summary>
        /// Ensures that there is a current station.
        /// </summary>
        /// <returns>
        /// True, if a current station is ensured.
        /// False, if a current station cannot be ensured.
        /// </returns>
        public bool EnsureCurrentStation()
        {
            // is there a station to ensure at all?
            if (_stations.Count == 0) {
                _currentStationIndex = -1;
                return false;
            }

            // defaults to the first station
            if (_currentStationIndex >= _stations.Count || _currentStationIndex < 0)
                _currentStationIndex = 0;


            return _currentStationIndex != -1;
        }

        /// <summary>
        /// Ensures that there is a play engine to play with.
        /// </summary>
        /// <returns>
        /// True, if a play engine can be ensured.
        /// False, if a play engine could not be ensured.
        /// </returns>
        protected bool EnsurePlayEngine()
        {
            // injection is no longer support, too bloated and enterprisey
            // TODO: Look for an alternative way to inject a play engine. Reflection is too slow.
            return _engine != null;
        }

        // Play controls

        public void Play()
        {
            // validate some conditions
            if (!EnsureCurrentStation()) {
                throw new InvalidOperationException(
                    "No station available for playing. Please fill the radio with" +
                    " stations first.");
            }

            if (!EnsurePlayEngine()) {
                throw new InvalidOperationException(
                    "Unable to load a play engine for playing. Please create and" +
                    " specify a play engine first.");
            }

            // can now play
            var urlToPlay = _stations[_currentStationIndex].StationUrl;

            // only issue play once, dont repeat if station hasn't changed
            if (_engine.CurrentUrl == urlToPlay && _engine.IsPlaying)
                return;

            _engine.Play(urlToPlay);

            if (_engine.IsPlaying)
                OnPlayStateChanged(new EventArgs());
        }

        public void Stop()
        {
            if (!EnsurePlayEngine()) {
                throw new InvalidOperationException(
                    "Unable to load a play engine for stopping. Please create and specify a play engine first.");
            }

            // only issue stop once, if engine already stopped, dont issue again
            if (!_engine.IsPlaying)
                return;

            _engine.Stop();

            if (!_engine.IsPlaying)
                OnPlayStateChanged(new EventArgs());
        }

        // Configuration

        public static Radio LoadFrom(Stream s)
        {
            return s.DeserializeBinaryOrXml<Radio>();
        }

        public void SaveXmlTo(Stream s)
        {
            this.XmlSerialize(s);
        }

        public void SaveBinaryTo(Stream s)
        {
            this.BinarySerialize(s);
        }
    }
}